应用层事件分发

事件处理(View)

基于 Android29,View 的事件处理入口 dispatchTouchEvent

boolean dispatchTouchEvent(MotionEvent event)

传递 ACTION_DOWN 事件给 View 或该 View 是 target 了;true 表示消费事件,false 为消费

// View#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) { // 校验window或事件是否OBSCURED
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { // View是否enabled
            result = true;
        }
        
        // View可用,设置了OnTouchListener
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) { // onTouch返回了返回,就消费了事件
            result = true;
        }

        if (!result && onTouchEvent(event)) { // result返回true,就不会调用onTouchEvent了
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}
  1. 事件先交给 accessibility event.isTargetAccessibilityFocus() 处理
  2. View 要 Enable

onTouchEvent(MotionEvent event)   处理单击,长按

// View#onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    if ((viewFlags & ENABLED_MASK) == DISABLED) { // View disabled
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable; // 如果一个View设置为disable了,但设置了click和longclick监听,依然会consume消费事件,只是不会响onClick或onLongClick监听而已
    }
    
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
                if (!clickable) { // 不可点击,移除tap、长按监听
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        // The button is being released before we actually
                        // showed it as pressed.  Make it show the pressed
                        // state now (before scheduling the click) to ensure
                        // the user sees it.
                        setPressed(true, x, y);
                    }

                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        removeLongPressCallback();

                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal(); // 点击声音,回调
                            }
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                mHasPerformedLongPress = false;

                if (!clickable) {
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    break;
                }

                if (performButtonActionOnTouchDown(event)) {
                    break;
                }

                // Walk up the hierarchy to determine if we're inside a scrolling container.
                boolean isInScrollingContainer = isInScrollingContainer();

                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); // 检测longPress事件,在500ms内没有click事件,那么执行onLongClick
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;

            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    drawableHotspotChanged(x, y);
                }

                final int motionClassification = event.getClassification();
                final boolean ambiguousGesture =
                        motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
                int touchSlop = mTouchSlop;
                if (ambiguousGesture && hasPendingLongPressCallback()) {
                    final float ambiguousMultiplier =
                            ViewConfiguration.getAmbiguousGestureMultiplier();
                    if (!pointInView(x, y, touchSlop)) {
                        // The default action here is to cancel long press. But instead, we
                        // just extend the timeout here, in case the classification
                        // stays ambiguous.
                        removeLongPressCallback();
                        long delay = (long) (ViewConfiguration.getLongPressTimeout()
                                * ambiguousMultiplier);
                        // Subtract the time already spent
                        delay -= event.getEventTime() - event.getDownTime();
                        checkForLongClick(
                                delay,
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    touchSlop *= ambiguousMultiplier;
                }

                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, touchSlop)) {
                    // Outside button
                    // Remove any future long press/tap checks
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }

                final boolean deepPress =
                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                if (deepPress && hasPendingLongPressCallback()) {
                    // process the long click action immediately
                    removeLongPressCallback(); // MOVE事件移除longPress的callback
                    checkForLongClick(
                            0 /* send immediately */,
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                }

                break;
        }

        return true;
    }

    return false;
}

View 事件处理总结

OnTouchListener 和 OnClickListener

mOnTouchListener.onTouch(this, event) 该 View 的 onTouch() 返回值
返回 true,设置了 OnTouchListener 监听,onTouch() 返回 true,View 的 onTouchEvent() 不会执行
返回 false,没有设置 OnTouchListener 监听或者 onTouch() 返回 false,执行 onTouchEvent() 方法
注: 如果要屏蔽 onTouchEvent() 事件,直接在 onTouch() 中返回 true 就行了

View disable 是否可消费事件?

一个 View 设置为了 disable,但是其可 clickable 或 longclickable,那么也会消费该事件,只是不会调用 onClick() 和 onLongClick() 方法。

单击事件和长按事件区别

  1. 单击:按下 (down),然后在 500ms 内抬起来 (up)
  2. 长按:按下 (down,超过 500ms),不需要 up 起来
  3. 单击事件,按下和抬起时间间隔小于 500ms,属于单击事件;按下超过 500ms,那么会响应长按事件,再响应单击事件

View 的长按事件,都是在 DOWN 事件中,通过 postDelayed(),默认延迟 500 毫秒,如果超过 500ms 没有 UP 事件或这期间没有 MOVE 事件到来,那么认为是长按事件,调用 onLongClick() 方法;超过了 500ms,认为是单击事件,调用 onClick() 方法。

事件分发(ViewGroup)

分发流程

Activity#dispatchTouchEvent →

PhoneWindow#superDispatchTouchEvent → 

DecorView(FrameLayout)#superDispatchTouchEvent → 

ViewGroup#dispatchTouchEvent

小结

  1. 每次 ACTION_DOWN 事件到来,代表了一套新的事件,会清除之前的 touch target
  2. 事件拦截只在 ACTION_DOWN 才能拦截
  3. 只 childvisible 或 animation!=null,才有机会决定自己是否处理事件
  4. 如果 Child DOWN 事件没有拦截,但是在 MOVE 或者 UP 的时候进行了拦截;那么在第一次会将事件设置为 CANCEL 事件,并清空 mMotionTarget,直接返回 true;

事件分发的入口 ViewGroup#dispatchTouchEvent,从这里开始分析

// ViewGroup#dispatchTouchEvent 基于Android29
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // ...

    boolean handled = false; // 
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // ACTION_DOWN事件开始,清除所有的touch target(mFirstTouchTarget)
        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Throw away all previous state when starting a new touch gesture.
            // The framework may have dropped the up or cancel event for the previous gesture
            // due to an app switch, ANR, or some other state change.
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // Check for interception.
        final boolean intercepted; // 是否拦截,true表示拦截,false表示不拦截
        // 首次ACTION_DOWN事件
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) { 
            // 看看子View有没有通过requestDisallowInterceptTouchEvent请求父容易不要拦截事件,true表示子View期望父容易不要拦截事件,false表示父容器能拦截事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) { 
                // disallowIntercept=false,看父容器onInterceptTouchEvent返回,true拦截,false不拦截
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            // 不是ACTION_DOWN事件或mFirstTouchTarget不为null,父容器拦截事件
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // Check for cancelation.
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {

            // If the event is targeting accessibility focus we give it to the
            // view that has accessibility focus and if it does not handle it
            // we clear the flag and dispatch the event to all children as usual.
            // We are looking up the accessibility focused host to avoid keeping
            // state since these events are very rare.
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                // childIndex points into presorted list, find original index
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        // The accessibility focus didn't handle the event, so clear
                        // the flag and do a normal dispatch to all children.
                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                // 只有down事件进来,这个处理意味着多指事件下只有一个child能响应down事件,那不管你其他指头落在哪里,都默认是这个view处理(已经有一个view消费了,再触摸到其他的未消费事件的view上执行)
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // Did not find a child to receive the event.
                    // Assign the pointer to the least recently added target.
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        // 首次ACTION_DOWN事件到来,mFirstTouchTarget为null
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // Dispatch to touch targets, excluding the new touch target if we already
            // dispatched to it.  Cancel touch targets if necessary.
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

一套事件有一个 ACTION_DOWN 事件,0 或多个 ACTION_MOVE 事件,1 个 ACTION_UP 事件

onInterceptTouchEvent 总结

你说说 viewgroup 的 onInterceptTouchEvent 执行情况,有子 view 消费和无子 view 消费的情况

public boolean dispatchTouchEvent(MotionEvent ev) {
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) { // Down事件或者mFirstTouchTarget不为空(有子view消费事件)
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) { // 子类未设置FLAG_DISALLOW_INTERCEPT标记,父容器是否拦截事件取决onInterceptTouchEvent
            intercepted = onInterceptTouchEvent(ev); // onInterceptTouchEvent返回false不拦截,返回true拦截
            ev.setAction(action); // restore action in case it was changed
        } else { // 子类设置了FLAG_DISALLOW_INTERCEPT标记通知父容器不拦截事件,那么父容器不拦截事件
            intercepted = false;
        }
    } else { // 不是Down事件且mFirstTouchTarget为null,默认拦截(即未找到子view消费事件,那么MOVE/UP事件父容器默认拦截)
        intercepted = true;
    }
    TouchTarget newTouchTarget = null;
    if (!canceled && !intercepted) { // 事件未取消且事件未被拦截
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            final View[] children = mChildren;
            for (int i = childrenCount - 1; i >= 0; i--) {
                final boolean handled;
                handled = child.dispatchTouchEvent(event); // 倒序遍历子view分发事件
                if(handled) {
                    newTouchTarget = addTouchTarget(child, idBitsToAssign); // mFirstTouchTarget在这赋值,找到可消费事件的子View
                    break;
                }
            }
        }
    }
    if (mFirstTouchTarget == null) { // 未找到可消费事件的子view,调用view#dispatchTouchEvent,最后调用onTouchEvent自己处理事件
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else { // 找到了可消费事件的子view
        TouchTarget target = mFirstTouchTarget;
        while (target != null) { // 遍历子mFirstTouchTarget
            final TouchTarget next = target.next;
            if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { // 分发事件给mFirstTouchTarget
                handled = true;
            }
            target = next;
        }
    }
}

onInterceptTouchEvent 返回 false

  1. onInterceptTouchEvent 返回 false,即父容器不不拦截事件,此时 Down 事件会遍历子控件看是否消费事件,但子 View 未找到消费的事件;这个时候会调用父控件自身的 onTouchEvent 处理事件(down 事件父控件的 onInterceptTouchEvent 和子控件都会走;后续的 move/up 事件,父控件的 onInterceptTouchEvent 和所有子 view 都不会接收该事件了)
  2. onInterceptTouchEvent 返回 false,即父容器不拦截事件,此时 Down 事件会遍历子控件看是否消费事件,找到了一个子 View 能消费事件;这个时候 mFirstTouchTarget 就不为 null 有值了,就会将后续 move/up 交给 mFirstTouchTarget 消费(down 事件父控件的 onInterceptTouchEvent 和子控件都会走;后续的 move/up 事件,父控件的 onInterceptTouchEvent 和消费事件的子控件会走)

onInterceptTouchEvent 返回 true

  1. onInterceptTouchEvent 返回 true,即父容器拦截该事件,此时 Down 事件不会经过子控件(也就是子控件收不到任何 down/move/up 事件),会调用父控件自身的 onTouchEvent 事件处理(down 事件父控件的 onInterceptTouchEvent 会走,子控件不走;后续 move/up 事件父控件的 onInterceptTouchEvent 不会走)

ACTION_DOWN 事件

  1. 首先看是否拦截事件,intercepted 为 true 表拦截,false 不拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}
  1. 如果 intercepted=true,拦截了事件,ViewGroup 自己处理事件,走 dispatchTransformedTouchEvent()
if (mFirstTouchTarget == null) {
    // 没有touch targets所以当成普通的view处理
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS); // canceled此时为false,child为null
} else {
    /// ...
}

接着走 dispatchTransformedTouchEvent()

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // If for some reason we ended up in an inconsistent state where it looks like we
    // might produce a motion event with no pointers in it, then drop the event.
    if (newPointerIdBits == 0) {
        return false;
    }

    // If the number of pointers is the same and we don't need to perform any fancy
    // irreversible transformations, then we can reuse the motion event for this
    // dispatch as long as we are careful to revert any changes we make.
    // Otherwise we need to make a copy.
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                // child为null,调用View#dispatchTouchEvent
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }

        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

调用 View#dispatchTouchEvent 处理事件

  1. 如果 intercepted=false,不拦截事件,寻找 touch target(mFirstTouchTarget)
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (actionMasked == MotionEvent.ACTION_DOWN) { // 为ACTION_DOWN事件
    final int childrenCount = mChildrenCount;
    if (newTouchTarget == null && childrenCount != 0) { // newTouchTarget为null且childrenCount不为0(有孩子)
        final View[] children = mChildren;
        for (int i = childrenCount - 1; i >= 0; i--) { // 拿最后一个孩子
            if (!child.canReceivePointerEvents() && !isTransformedTouchPointInView(x, y, child, null)) { // 判断child是否VISIBLE||mCurrentAnimation!=null 叽叽判断触摸点是否在childView上,不在的话continue,不分发事件
                continue;
            }
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 调用子View的dispatchTouchEvent处理事件
                // Child wants to receive touch within its bounds.
                newTouchTarget = addTouchTarget(child, idBitsToAssign); // 首次ACTION_DOWN事件,target.next=null,mFirstTouchTarget = 当前子View的target
                alreadyDispatchedToNewTouchTarget = true; // 为true
                break; // 找到一个可以消费事件的子View,结束事件分发了
            }
        }
        if (newTouchTarget == null && mFirstTouchTarget != null) { // 首次ACTION_DOWN事件,找到了newTouchTarget不为null,不走这段逻辑
            // Did not find a child to receive the event.
            // Assign the pointer to the least recently added target.
            newTouchTarget = mFirstTouchTarget;
            while (newTouchTarget.next != null) {
                newTouchTarget = newTouchTarget.next;
            }
            newTouchTarget.pointerIdBits |= idBitsToAssign;
        }
    }
}

// View#pointInView,判断触摸点是否在View上
public boolean pointInView(float localX, float localY, float slop) {
    return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
            localY < ((mBottom - mTop) + slop);
}

// 获取一个TouchTarget
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

看看找到了一个 touch target(看 mFirstTouchTarget!=null 了)

if (mFirstTouchTarget == null) { // 不拦截事件,未找到一个子View,
   // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else { // 不拦截事件,找到了一个子View(touch target),现在走这里 ,首次ACTION_DOWN事件,走到这里,已经事件被消费了,在这里什么也没做
    // Dispatch to touch targets, excluding the new touch target if we already
    // dispatched to it.  Cancel touch targets if necessary.
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next; // 首次mFirstTouchTarget=null
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // 首次ACTION_DOWN事件,如果找到了touch target,那么alreadyDispatchedToNewTouchTarget=true
            handled = true;
        } else {
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

到这里,ACTION_DOWN 事件的拦截和非拦截都分析完毕

  1. 拦截,调用 View#dispatchTouchEvent 处理事件 (此处 ViewGroup 自己处理事件)
  2. 不拦截,未找到一个 child 能消费事件(mFirstTouchTarget=null),调用 View#dispatchTouchEvent 处理事件 (此处 ViewGroup 自己处理事件)
  3. 不拦截,找到一个 child 能消费事件(mFirstTouchTarget!=null),child#dispatchTouchEvent 处理事件(此处由指定的 child 处理事件)

ACTION_MOVE 事件

首次 MOVE_ 事件

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) { // 现在mFirstTouchTarget不为null,进来
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

看看父容器是否拦截事件

TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
    // MOVE事件这里什么也没做
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) { // MOVE事件,之前未找到child touch target,那么还是调用父容器View.dispatchTouchEvent自己处理事件
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else { // MOVE事件,之前有找到child touch target
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // MOVE事件alreadyDispatchedToNewTouchTarget=false
            handled = true;
        } else {
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;  // MOVE事件,如果DOWN事件找到了要分发的子View,现在父容器要拦截掉该事件,那么需要取消分发Cancel事件给子View
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {  // 之前将事件分发给自己找到的child touch target
                handled = true;
            }
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next; // mFirstTouchTarget只为null了
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}
// 此时为MOVE事件,acncel为true,child不为null
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    final boolean handled;

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { // 如果事件被取消了,分发cancel给子view,这次MOVE事件到此为止
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    // ...
}

到这里,首次 ACTION_MOVE 事件的拦截和非拦截都分析完毕

  1. 拦截,之前未找到一个 child 能消费事件,调用 View#dispatchTouchEvent 处理事件 (此处 ViewGroup 自己处理事件)
  2. 拦截,之前找到一个 child 能消费事件,给 child 分发个 cancel 事件
  3. 不拦截,之前未找到一个 child 能消费事件(mFirstTouchTarget=null),调用 View#dispatchTouchEvent 处理事件 (此处 ViewGroup 自己处理事件)
  4. 不拦截,之前找到一个 child 能消费事件(mFirstTouchTarget!=null),child#dispatchTouchEvent 处理事件(此处由指定的 child 处理事件)

第 2+ 次 MOVE 时间

无 touch target

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

无 touch target,直接调用父 View 自己的 View#dispatchTouchEvent 处理时间

有 touch target,拦截

有 touch target,在 MOVE 时被拦截了,那么 mFirstTouchTarge 为 null,同无 touch target 了

有 touch target,不拦截

TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
    final TouchTarget next = target.next;
    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
        handled = true;
    } else {
        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
        if (dispatchTransformedTouchEvent(ev, cancelChild,
                target.child, target.pointerIdBits)) {
            handled = true;
        }
        if (cancelChild) {
            if (predecessor == null) {
                mFirstTouchTarget = next;
            } else {
                predecessor.next = next;
            }
            target.recycle();
            target = next;
            continue;
        }
    }
    predecessor = target;
    target = next;
}

有 touch target,不拦截,子 View 调用自己的 dispatchTouchEvent

ACTION_UP 事件

情况状态,清除 mFirstTouchTarget

if (canceled
        || actionMasked == MotionEvent.ACTION_UP
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    resetTouchState();
}
private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

事件冲突解决

内部拦截法解决 requestDisallowInterceptTouchEvent

  1. 如果父容器在 down 事件就给 intecept 了,那么子 view 收不到任何事件,子 view 的 dispatchTouchEvent 也不会调用,也就用不了内部解决冲突问题了
  2. 如果父容器 down 事件不 intecept,子 view 可以收到 down 事件并消费,但在 move 事件时给拦截了,可以内部调用 View#requestDisallowIntecept(disallow) 解决事件冲突, 设置 true 递归父容器不拦截,false 拦截
  3. 要在 down 调用 requestDisallowInterceptTouchEvent(true),事件冲突一般都是在 move 来解决;在 up 时恢复 requestDisallowInterceptTouchEvent(false)
public class MyListView extends ListView {
    public MyListView(Context context) {
        super(context);
    }
    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    float startX = 0F;
    float startY = 0F;
    // 内部拦截法:子view处理事件冲突
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                startX = ev.getX();
                startY = ev.getY();
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                float deltX = ev.getX() - startX;
                float deltY = ev.getY() - startY;
                if (Math.abs(deltX) > Math.abs(deltY)) {
                    requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                requestDisallowInterceptTouchEvent(false);
                break;
            default:
                break;
        }
        boolean b = super.dispatchTouchEvent(ev);
        return b;
    }
}

外部拦截法解决 onInterceptTouchEvent

外部拦截法:父容器处理冲突
父容器 onInterceptTouchEvent,我想要把事件分发给谁就分发给谁

public class BadViewPager extends ViewPager {
    private int mLastX, mLastY;
    public BadViewPager(@NonNull Context context) {
        super(context);
    }
    public BadViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    // 外部拦截法:父容器处理冲突
    // 我想要把事件分发给谁就分发给谁
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
//        if (event.getAction() == MotionEvent.ACTION_DOWN){
//            super.onInterceptTouchEvent(event);
//            return false;
//        }
//        return true;

        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mLastX = (int) event.getX();
                mLastY = (int) event.getY();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    return true;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
            default:
                break;
        }
        return super.onInterceptTouchEvent(event);
    }
}